package com.raymond.algorithm.currentlimit;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 计算器算法
 *
 * 计数器法是限流算法里最简单也是最容易实现的一种算法，
 * 一般我们会限制一秒钟能够通过的请求数。
 * 比如我们规定，对于A接口来说，我们1分钟的访问次数不能超过100个。
 * 那么我们可以这么做：在一开始的时候，我们可以设置一个计数器counter，
 * 每当一个请求过来的时候， counter就加1，
 * 如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内，那么说明请求数过多;
 * 如果该请求与第一个请求的间隔时间大于1分钟，且counter的值还在限流范围内，那么就重置 counter。
 *
 * 弊端
 * 假设有一个恶意用户，他在0:59时，瞬间发送了100个请求，并且1:00又瞬间发送了100个请求，
 * 那么其实这个用户在 1秒里面，瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求，
 * 也就是每秒钟最多1.7个请求，用户通过在时间窗口的重置节点处突发请求， 可以瞬间超过我们的速率限制。
 * 用户有可能通过算法的这个漏洞，瞬间压垮我们的应用
 *
 * @author :  raymond
 * @version :  V1.0
 * @date :  2021-04-22 14:35
 */
public class Counter {
    /**
     * 开始时间
     */
    private long time = System.currentTimeMillis();
    /**
     * 计数器
     */
    private AtomicInteger atomicInteger = new AtomicInteger();
    /**
     * 时间窗口的最大请求数
     */
    private final int limit;
    /**
     * 时间窗口范围(单位:毫秒)
     */
    private final long interval;

    /**
     * 默认限流配置
     * limit:100
     * interval:60s
     */
    public Counter() {
        this(100, 60 * 1000);
    }

    /**
     *
     * @param limit 时间窗口最大请求数
     * @param interval 时间窗口范围(单位:毫秒)
     */
    public Counter(int limit, long interval) {
        this.limit = limit;
        this.interval = interval;
    }

    /**
     * 限流
     * true 限流了
     * false 允许正常访问
     * @return true,false
     */
    public boolean limit() {
        long now = System.currentTimeMillis();
        if (now < time + interval) {
            return limit < atomicInteger.incrementAndGet();
        }
        time = now;
        atomicInteger.set(1);
        return false;
    }

    public static void main(String[] args) throws InterruptedException {
        suc();
        err();
    }

    /**
     * 可正常过滤并发请求
     * @throws InterruptedException 异常
     */
    public static void suc() throws InterruptedException {
        Counter counter = new Counter(100,  1000);
        for (int i = 0; i < 200; i++) {
            boolean limit = counter.limit();
            if (limit) {
                System.out.println(i);
                TimeUnit.SECONDS.sleep(1);
            }

        }
    }

    /**
     * 要求十秒请求不超过一百个
     * 第九秒请求一百个,十秒后又请求一百个,两秒共请求两百个,没有达到限流要求
     *
     * @throws InterruptedException 异常
     */
    public static void err() throws InterruptedException {
        Counter counter = new Counter(100, 10 * 1000);
        TimeUnit.SECONDS.sleep(9);
        for (int i = 0; i < 200; i++) {
            if (i == 100) {
                TimeUnit.SECONDS.sleep(1);
            }
            boolean limit = counter.limit();
            if (limit) {
                System.out.println(i);
            }

        }
    }
}
